
// Copyright (C) 2007 Richard Uhler
// This file is part of Esiply.
//
// Esiply is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License, Version 2,
// as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

#include <assert.h>
#include <iostream>

#include "Array.h"
#include "Boolean.h"
#include "ErrorObject.h"
#include "Null.h"
#include "Number.h"
#include "String.h"
#include "Undefined.h"
#include "Value.h"
#include "ValueType.h"

Array* Array::prototype = new Array(Null::null, Object::prototype);

Array::Array()
    : Object(Null::null, Array::prototype, new String("Array"))
{
    // Make sure we always have a "length" property
    Object::put(new String("length"), new Number(0),
            PA_DONT_ENUM | PA_DONT_DELETE);
}

Array::Array(Value* value, Object* prototype)
    : Object(value, prototype, new String("Array"))
{
    // Make sure we always have a "length" property
    Object::put(new String("length"), new Number(0),
            PA_DONT_ENUM | PA_DONT_DELETE);
}

void Array::put(String* name, Value* value, unsigned char attrib)
{
    // Ecma 15.4.5.1
    // let A be this array, P be the property name
    // 1. Call the [[CanPut]] method of A with P 
    // 2. If result(1) is false, return.
    if (!canPut(name))
    {
        return;
    }

    // 3. If A doesn't have a property with name P, go to step 7.
    String* length = new String("length");
    if (Object::has(name))
    {
        // 4. If P is "length" go to step 12
        if (String::equals(name, length)->isTrue())
        {
            // 12. Compute toUint32(v)
            Number* asu32 = value->toUint32();

            // 13. If that's not equal to toNumber(v),
            if (!Number::equals(asu32, value->toNumber())->isTrue())
            {
                // TODO: huh? how do I throw in something that doesn't return?
                //return esiply_throw(ET_RANGE, "length value is not a number");
                assert(false && "length value is not a number");
            }

            // 14. For every integer k that is less than the value of the
            // length property of A, but not less than result(12)
            // if A itself has a propert named toString(k), then delete
            // that property. TODO: huh? I think this means delete any integer
            // values we have between asu32 and the current length
            // property
            Value* curlen = get(length)->toUint32();
            for (int i = (int)asu32->number()->get_value();
                    i < (int)curlen->number()->get_value(); i++)
            {
                String* index = (new Number(i))->toString();
                if (has(index))
                {
                    this->deleteProperty(index);
                }
            }
        }

        // 5. Set the value of property P of A to V
        Object::put(name, value, attrib);

        // 6. Go to step 8
    }
    else
    {
        // 7. Create a property with name P, set it's value to V,
        // and give it empty attributes
        // Note: we DONT give it empty attributes in case the caller
        // of the put function provided something better.
        Object::put(name, value, attrib);
    }

    // 8. If P is not an array index, return
    if (!isArrayIndex(name))
    {
        return;
    }

    // 9. If toUint32(p) is less than the value of the length property
    // of A, then return
    if (Number::less_than(name->toUint32(), get(length)->number())->isTrue())
    {
        return;
    }

    // 10. Change (or set) the value of the length property of A
    // to Uint32(p) + 1
    Number* one = new Number(1);
    Object::put(length, Number::add(name->toUint32(), one),
            PA_DONT_ENUM | PA_DONT_DELETE); 

    // 11. Return
}

bool Array::isArrayIndex(Value* p)
{
    // Ecma 15.
    // A property name P is an array index if and only if toString(toUint32(p))
    // is equal to P, and toUint32(P) is not equal to 2^32-1
    Number* two_32m1 = new Number(0xFFFFFFFF);
    Number* as_num = p->toUint32();
    if (Value::equals(p, as_num->toString())->isTrue()
            && !Number::equals(as_num, two_32m1)->isTrue())
    {
        return true;
    }
    return false;
}
